﻿using System;
using System.Collections.Generic;
using System.Threading;
using Allegro.MathLib;

namespace Allegro.MathLib
{
    /// <summary>
    /// Spline pool is an object pool for splines. The pool may have multiple entries, each entry given
    /// by an index for fast access. The purpose of the pool is to reuse splines in a multithreaded environment.
    /// It is evident that it is more efficient to reuse a spline instead of recreating it between uses.
    /// Furthermore, it is not efficient to let one spline be used by several threads, because one spline will evaluate considerably
    /// faster when evaluated incrementally from a single thread (fast forward/backward evaluation). 
    /// The SplinePool should be cached using the Cosmologica.Services.Cache class.
    /// </summary>
    public class SplinePool
    {
        private string _poolName;
        Dictionary<string, double[]> _xDataDictionary;
        Dictionary<string, double[]> _yDataDictionary;
        Dictionary<string, double[]> _uDataDictionary;
        Dictionary<string, double[]> _dy2DataDictionary;
        private Dictionary<string, Stack<Spline>> _splinePools;

        public SplinePool(string poolName)
        {
            _poolName = poolName;
            _xDataDictionary = new Dictionary<string, double[]>();
            _yDataDictionary = new Dictionary<string, double[]>();
            _uDataDictionary = new Dictionary<string, double[]>();
            _dy2DataDictionary = new Dictionary<string, double[]>();
            _splinePools = new Dictionary<string, Stack<Spline>>();
        }

        /// <summary>
        /// Name of the pool
        /// </summary>
        public string Name
        {
            get { return _poolName; }
        }
        /// <summary>
        /// Add data for a new spline
        /// </summary>
        /// <param name="key">Key that must be used to retrive splines from these data</param>
        /// <param name="xvalues"></param>
        /// <param name="yvalues"></param>
        /// <returns></returns>
        public void AddSplineData(string key, double[] xvalues, double[] yvalues)
        {
            Monitor.Enter(_xDataDictionary);
            if (!_xDataDictionary.ContainsKey(key))
            {
                _xDataDictionary.Add(key, xvalues);
                Monitor.Enter(_xDataDictionary[key]);
                _yDataDictionary.Add(key, yvalues);
                _splinePools.Add(key, new Stack<Spline>());
                Monitor.Exit(_xDataDictionary[key]);
            }
            Monitor.Exit(_xDataDictionary);
        }
        /// <summary>
        /// Add spline data from an existing spline. The source data are sucked from
        /// the spline and reused to create more splines in the pool. The spline is pooled.
        /// </summary>
        /// <param name="key">Key that must be used to retrieve splines from the data</param>
        /// <param name="spline"></param>
        /// <returns></returns>
        public void AddSplineData(string key, Spline spline)
        {
            double[] xvalues = spline.x;
            double[] yvalues = spline.y;
            double[] uvalues = spline.u;
            double[] dy2values = spline.dy2;
            Stack<Spline> splines = new Stack<Spline>();
            splines.Push(spline);

            Monitor.Enter(_xDataDictionary);
            // Double check in case of race condition
            if (!_xDataDictionary.ContainsKey(key))
            {
                _xDataDictionary.Add(key, xvalues);
                Monitor.Enter(_xDataDictionary[key]);
                _yDataDictionary.Add(key, yvalues);
                _uDataDictionary.Add(key, uvalues);
                _dy2DataDictionary.Add(key, dy2values);
                _splinePools.Add(key, splines);
                Monitor.Exit(_xDataDictionary[key]);
            }
            Monitor.Exit(_xDataDictionary);
        }
        /// <summary>
        /// Take a spline from the stack if it exists, otherwise create it
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public Spline Checkout(string key)
        {
            if (_xDataDictionary.ContainsKey(key))
            {
                Spline spline = null;
                Monitor.Enter(_xDataDictionary[key]);
                if (_splinePools[key].Count > 0)
                {
                    spline = _splinePools[key].Pop();
                }
                Monitor.Exit(_xDataDictionary[key]);
                if (spline == null)
                {
                    double[] u = _uDataDictionary.ContainsKey(key) ? _uDataDictionary[key] : null;
                    double[] dy2 = _dy2DataDictionary.ContainsKey(key) ? _dy2DataDictionary[key] : null;
                    if (u != null && dy2 != null)
                    {
                        spline =
                            new Spline(_xDataDictionary[key], _yDataDictionary[key], _uDataDictionary[key],
                                       _dy2DataDictionary[key]);
                    }
                    else
                    {
                        spline = new Spline(_xDataDictionary[key], _yDataDictionary[key]);
                        _uDataDictionary.Add(key, spline.u);
                        _dy2DataDictionary.Add(key, spline.dy2);
                    }
                }
                return spline;
            }
            else
            {
                throw new ArgumentException(string.Format("SplinePool contains no entry with key {0}", key));
            }
        }
        /// <summary>
        /// Return a spline that is no longer in use. This allows the stack of splines to grow
        /// </summary>
        /// <param name="key"></param>
        /// <param name="spline"></param>
        public void Checkin(string key, Spline spline)
        {
            if (_xDataDictionary.ContainsKey(key) && spline != null)
            {
                Monitor.Enter(_xDataDictionary[key]); 
                _splinePools[key].Push(spline);
                Monitor.Exit(_xDataDictionary[key]);
            }
            else if (spline != null)
            {
                AddSplineData(key, spline);
            }
        }

        /// <summary>
        /// Returns true if data exists for this key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool HasSpline(string key)
        {
            return _xDataDictionary.ContainsKey(key);
        }
    }
}
